<?php
namespace Illuminate\Auth;

use Illuminate\Contracts\Auth\Factory as FactoryContract;

use Closure;

use InvalidArgumentException;

class AuthManager implements FactoryContract {
    use CreatesUserProviders;

    protected $app; // app
    protected $customCreators = []; // 驱动
    protected $guards = []; // 守护者
    protected $userResolver;

    public function __construct($app) {
        $this->app = $app;

        $this->userResolver = function ($guard = null) {
            return $this->guard($guard)->user();
        };
    }

    public function guard($name = null) {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    protected function resolve($name) {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth driver [{$config['driver']}] for guard [{$name}] is not defined.");
    }

    protected function callCustomCreator($name, array $config) { // 自定义驱动
        return $this->customCreators[$config['driver']]($this->app, $name, $config);
    }

    public function createSessionDriver($name, $config) {
        $provider = $this->createUserProvider($config['provider'] ?? null); // 提供器

        $guard = new SessionGuard($name, $provider, $this->app['session.store']); // session 守护器

        if ( method_exists($guard, 'setCookieJar') ) $guard->setCookieJar($this->app['cookie']); // cookie
        if ( method_exists($guard, 'setDispatcher') ) $guard->setDispatcher($this->app['events']); // 事件
        if ( method_exists($guard, 'setRequest') ) $guard->setRequest($this->app->refresh('request', $guard, 'setRequest')); // 守护

        return $guard;
    }

    public function createTokenDriver($name, $config) {
        $guard = new TokenGuard(
            $this->createUserProvider($config['provider'] ?? null),
            $this->app['request']
        );

        $this->app->refresh('request', $guard, 'setRequest');

        return $guard;
    }

    protected function getConfig($name) { // auth guard 守卫者
        return $this->app['config']["auth.guards.{$name}"];
    }
    public function getDefaultDriver() { // 获取 default guard 默认守卫者
        return $this->app['config']['auth.defaults.guard'];
    }
    public function setDefaultDriver($name) { // 设置
        $this->app['config']['auth.defaults.guard'] = $name;
    }

    public function shouldUse($name) { // 设置 guard
        $name = $name ?: $this->getDefaultDriver();
        $this->setDefaultDriver($name); // 设置默认驱动

        $this->userResolver = function ($name = null) { // 返回用户
            return $this->guard($name)->user();
        };
    }

    public function viaRequest($driver, callable $callback) { // 扩展驱动
        return $this->extend($driver, function () use ($callback) {
            $guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());

            $this->app->refresh('request', $guard, 'setRequest');

            return $guard;
        });
    }

    public function userResolver() { // userResolver
        return $this->userResolver;
    }
    public function resolveUsersUsing(Closure $userResolver) {
        $this->userResolver = $userResolver;

        return $this;
    }

    public function extend($driver, Closure $callback) { // 自定义驱动
        $this->customCreators[$driver] = $callback;

        return $this;
    }
    public function provider($name, Closure $callback) { // 自定义提供者
        $this->customProviderCreators[$name] = $callback;

        return $this;
    }

    public function __call($method, $parameters) {
        return $this->guard()->{$method}(...$parameters);
    }
}
